using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Microsoft.Surface;
using Microsoft.Surface.Core;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;

namespace SurfaceAirport
{
    /// <summary>
    /// This is the main type for your application.
    /// </summary>
    public class App1 : Microsoft.Xna.Framework.Game
    {
        private readonly GraphicsDeviceManager graphics;
        private ContactTarget contactTarget;
        private Color backgroundColor = new Color(0, 0, 128);
        private bool applicationLoadCompleteSignalled;

        private UserOrientation currentOrientation = UserOrientation.Bottom;
        private Matrix screenTransform = Matrix.Identity;
        private Matrix inverted;
        // application state: Activated, Previewed, Deactivated,
        // start in Activated state
        private bool isApplicationActivated = true;
        private bool isApplicationPreviewed;

        private Texture2D airplane;
        private Texture2D airplaneRoute;
        private Texture2D airport;
        SpriteFont courierNew;

        private SpriteBatch airSprite;

        private float sizeOfPlaneHitbox;

        
        private int score = 0;
        private int highscore = 0; 
        private long nextSpawnOpp = 250;
        private float maxNrOfPlanes = 1.5f;
        private bool crashed = false;
        private int timeOfDeath = 0;
        private float surfaceWidth;
        private float surfaceHeight;
        private Vector2 airportLocation;
        Random rand = new Random();
        private int bannedId = -1;
        private float outsideBorders = 5f;
        private int waitAfterCrash = 5;
        private System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();


        private List<Airplane> airplanes = new List<Airplane>();

        private Dictionary<int, Airplane> oldContacts = new Dictionary<int, Airplane>();

        /// <summary>
        /// The graphics device manager for the application.
        /// </summary>
        protected GraphicsDeviceManager Graphics
        {
            get { return graphics; }
        }

        /// <summary>
        /// The target receiving all surface input for the application.
        /// </summary>
        protected ContactTarget ContactTarget
        {
            get { return contactTarget; }
        }

        /// <summary>
        /// Default constructor.
        /// </summary>
        public App1()
        {
            graphics = new GraphicsDeviceManager(this);
        }

        #region Initialization

        /// <summary>
        /// Moves and sizes the window to cover the input surface.
        /// </summary>
        private void SetWindowOnSurface()
        {
            System.Diagnostics.Debug.Assert(Window.Handle != System.IntPtr.Zero,
                "Window initialization must be complete before SetWindowOnSurface is called");
            if (Window.Handle == System.IntPtr.Zero)
                return;

            // We don't want to run in full-screen mode because we need
            // overlapped windows, so instead run in windowed mode
            // and resize to take up the whole surface with no border.

            // Make sure the graphics device has the correct back buffer size.
            InteractiveSurface interactiveSurface = InteractiveSurface.DefaultInteractiveSurface;
            if (interactiveSurface != null)
            {
                graphics.PreferredBackBufferWidth = interactiveSurface.Width;
                graphics.PreferredBackBufferHeight = interactiveSurface.Height;
                graphics.ApplyChanges();

                // Remove the border and position the window.
                Program.RemoveBorder(Window.Handle);
                Program.PositionWindow(Window);
            }
        }

        /// <summary>
        /// Initializes the surface input system. This should be called after any window
        /// initialization is done, and should only be called once.
        /// </summary>
        private void InitializeSurfaceInput()
        {
            System.Diagnostics.Debug.Assert(Window.Handle != System.IntPtr.Zero,
                "Window initialization must be complete before InitializeSurfaceInput is called");
            if (Window.Handle == System.IntPtr.Zero)
                return;
            System.Diagnostics.Debug.Assert(contactTarget == null,
                "Surface input already initialized");
            if (contactTarget != null)
                return;

            // Create a target for surface input.
            contactTarget = new ContactTarget(Window.Handle, EventThreadChoice.OnBackgroundThread);
            contactTarget.EnableInput();
        }

        /// <summary>
        /// Reset the application's orientation and transform based on the current launcher orientation.
        /// </summary>
        private void ResetOrientation()
        {
            UserOrientation newOrientation = ApplicationLauncher.Orientation;

            if (newOrientation == currentOrientation) { return; }

            currentOrientation = newOrientation;

            if (currentOrientation == UserOrientation.Top)
            {
                screenTransform = inverted;
            }
            else
            {
                screenTransform = Matrix.Identity;
            }
        }

        #endregion

        #region Helper functions
        //maybe should be put in another class, bet well...

        //mebe not strictly necessary
        private bool OnTarget(Vector2 plane, Vector2 target){
            if (Vector2.Distance(plane, target) <= sizeOfPlaneHitbox)
            {
                return true;
            }
            return false;
        }

        private bool PlaneOnTarget(Vector2 plane1, Vector2 plane2)
        {
            if (Vector2.Distance(plane1, plane2) <= 24f)
            {
                return true;
            }
            return false;
        }

        //to handle planes moving at specifik locations
        private void PlaneUpdateLogic()
        {
            foreach (Airplane plane in airplanes)
            {
                //wraps the field
                if (plane.Location.X < -outsideBorders)
                    plane.Location = new Vector2(surfaceWidth + outsideBorders, plane.Location.Y);
                else if (plane.Location.Y < -outsideBorders)
                    plane.Location = new Vector2(plane.Location.X, surfaceHeight + outsideBorders);
                else if (plane.Location.X > surfaceWidth + outsideBorders)
                    plane.Location = new Vector2(-outsideBorders, plane.Location.Y);
                else if (plane.Location.Y > surfaceHeight + outsideBorders)
                    plane.Location = new Vector2(plane.Location.X, -outsideBorders);
            }
            //this.Window.ClientBounds.Height
        }

        //spawns a plane along one random edge
        private void SpawnPlane()
        {
            Vector2 spawnLocation = Vector2.Zero;
            float spawnRotation;
            Vector2 spawnRotationGoal = Vector2.Zero;
            int side = rand.Next(4);
            float speed = (float)((float)rand.Next(30,130) / 100);

            switch (side)
            {
                case 0:
                    spawnLocation = new Vector2((float)rand.Next((int)surfaceWidth), -outsideBorders);
                    spawnRotationGoal = new Vector2((float)rand.Next(0, (int)surfaceWidth),
                        (float)rand.Next((int)surfaceHeight / 2, (int)surfaceHeight));
                    break;
                case 1:
                    spawnLocation = new Vector2(surfaceWidth + outsideBorders, (float)rand.Next((int)surfaceHeight));
                    spawnRotationGoal = new Vector2((float)rand.Next(0, (int)surfaceWidth / 2),
                        (float)rand.Next(0, (int)surfaceHeight));
                    break;
                case 2:
                    spawnLocation = new Vector2((float)rand.Next((int)surfaceWidth), surfaceHeight + outsideBorders);
                    spawnRotationGoal = new Vector2((float)rand.Next(0, (int)surfaceWidth),
                        (float)rand.Next(0, (int)surfaceHeight / 2));
                    break;
                case 3:
                    spawnLocation = new Vector2(-outsideBorders, (float)rand.Next((int)surfaceHeight));
                    spawnRotationGoal = new Vector2((float)rand.Next((int)surfaceWidth / 2, (int)surfaceWidth),
                        (float)rand.Next(0, (int)surfaceHeight));
                    break;
            }

            
            float deltaX = spawnRotationGoal.X - spawnLocation.X;
            float deltaY = spawnRotationGoal.Y - spawnLocation.Y;
            spawnRotation = (float)Math.Atan(deltaX / deltaY);
            if (deltaY > 0) //ugly as hell but it works and im to tierd to find a real solution....
            {
                spawnRotation = (float)Math.PI - spawnRotation;
            }
            else
            {
                spawnRotation = -spawnRotation;
            }

            airplanes.Add(new Airplane(spawnLocation, spawnRotation, speed));

        }


        //returns definite false above score*2. Belov that there is a (1+numberOfMissingPlanes)% to return true
        private bool ShouldPlaneSpawn()
        {
            if (airplanes.Count < score * maxNrOfPlanes)
            {
                if (rand.Next(100) < (9 + (score * maxNrOfPlanes - airplanes.Count)))
                {
                    return true;
                }
                else
                {
                    return false;
                }
             }
            else
            {
                return false;
            }


        }
        //Displayes the game over screen
        private void CrashScreen()
        {
            graphics.GraphicsDevice.Clear(Color.SlateGray);
            airSprite.Begin();
            //Draws score
            if (score > highscore)
                highscore = score;
            String gameOverInfoString = "Game Over. Final score: " + score + " Highscore: " + highscore 
                + "\nTo reset, wait " + waitAfterCrash + " seconds and then touch the screen";
            Vector2 scoreCenter = courierNew.MeasureString(gameOverInfoString) / 2;
            
            
            foreach (Airplane plane in airplanes)
            {
                airSprite.Draw(airplane, plane.Location, null, plane.Color, plane.Rotation, new Vector2(airplane.Width / 2, airplane.Height / 2), new Vector2(1F, 1F), SpriteEffects.None, 0f);
            }

            airSprite.DrawString(courierNew, gameOverInfoString, new Vector2(surfaceWidth / 2, surfaceHeight / 2), Color.Blue, 0f, scoreCenter, 0.5f, SpriteEffects.None, 1f);


            airSprite.End();
        }

        //resets the game
        private void reset()
        {
            airplanes.Clear();
            crashed = false;
            score = 0;
            SpawnPlane();
        }

        private bool FromRightDirection(Airplane plane)
        {
            if (plane.Rotation == Math.PI || plane.Rotation == -Math.PI ||
                plane.Rotation == (2 * Math.PI - Math.PI / 2) ||
                plane.Rotation == -Math.PI / 2)
            {
                return true;
            }
            return false;
        }


        private void CheckHighscore()
        {
            if (score == highscore)
            {
                byte[] val = BitConverter.GetBytes(highscore);
                byte[] retVal = md5.ComputeHash(val);
                String hash = BitConverter.ToString(retVal);

                StreamWriter file = new StreamWriter(@"Resources\scores.txt");
                file.WriteLine(highscore + " " + hash);
                file.Close();
            }
        }

        #endregion

        #region Overridden Game Methods

        /// <summary>
        /// Allows the app to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here

            SetWindowOnSurface();
            InitializeSurfaceInput();

            // Set the application's orientation based on the current launcher orientation
            currentOrientation = ApplicationLauncher.Orientation;

            // Subscribe to surface application activation events
            ApplicationLauncher.ApplicationActivated += OnApplicationActivated;
            ApplicationLauncher.ApplicationPreviewed += OnApplicationPreviewed;
            ApplicationLauncher.ApplicationDeactivated += OnApplicationDeactivated;

            // Setup the UI to transform if the UI is rotated.
            // Create a rotation matrix to orient the screen so it is viewed correctly
            // when the user orientation is 180 degress different.
            inverted = Matrix.CreateRotationZ(MathHelper.ToRadians(180)) *
                       Matrix.CreateTranslation(graphics.GraphicsDevice.Viewport.Width,
                                                 graphics.GraphicsDevice.Viewport.Height,
                                                 0);

            if (currentOrientation == UserOrientation.Top)
            {
                screenTransform = inverted;
            }

            try
            {
                String[] text = File.ReadAllLines(@"Resources\scores.txt");

                char[] delimiters = new char[] { ' ' };
                String[] split = text[0].Split(delimiters, 2);
                int hs = System.Convert.ToInt32(split[0]);
                byte[] val = BitConverter.GetBytes(hs);
                byte[] retVal = md5.ComputeHash(val);
                String s = BitConverter.ToString(retVal);

                if (s.Equals(split[1]))
                    highscore = hs;
                else
                    Console.WriteLine("Highscore fail");
            }
            catch (FileNotFoundException e) { }


            base.Initialize();
        }

        /// <summary>
        /// Load your graphics content.
        /// </summary>
        protected override void LoadContent()
        {
            // TODO: Load any content

            surfaceHeight = Window.ClientBounds.Height;
            surfaceWidth = Window.ClientBounds.Width;
            airportLocation = new Vector2(surfaceWidth / 2, surfaceHeight / 2);
            airSprite = new SpriteBatch(GraphicsDevice);
            airplane = Texture2D.FromFile(graphics.GraphicsDevice, @"Resources\airplane.png");
            airplaneRoute = Texture2D.FromFile(graphics.GraphicsDevice, @"Resources\dot3.png");
            airport = Texture2D.FromFile(graphics.GraphicsDevice, @"Resources\airport.png"); //using an airplane for now
            //Content.Load<SpriteFont>(@"Resources\Arial.spriteFont");
            

            //spriteFont = Texture.FromFile(graphics.GraphicsDevice, @"Resources\Arial.spriteFont");
            courierNew = Content.Load<SpriteFont>(@"Resources\Courier New");

            sizeOfPlaneHitbox = airplane.Width;
            //airplanes.Add(new Airplane(new Vector2(500,300), 1.57f));
            for (int i = 0; i < 1; i++)
            {
                SpawnPlane();
            }
        }

        /// <summary>
        /// Unload your graphics content.
        /// </summary>
        protected override void UnloadContent()
        {
            Content.Unload();
        }

        /// <summary>
        /// Allows the app to run logic such as updating the world,
        /// checking for collisions, gathering input and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void             Update(GameTime gameTime)
        {
            if (isApplicationActivated || isApplicationPreviewed)
            {
                if (isApplicationActivated)
                {

                    if (gameTime.TotalGameTime.TotalMilliseconds > nextSpawnOpp && !crashed)
                    {
                        if (airplanes.Count == 0 || ShouldPlaneSpawn())
                        {
                            SpawnPlane();
                        }
                        nextSpawnOpp += 500;
                    }
                    // TODO: Process contacts, 
                    // use the following code to get the state of all current contacts.
                    ReadOnlyContactCollection contacts = contactTarget.GetState();
                    if ((crashed) && (gameTime.TotalGameTime.TotalSeconds > (timeOfDeath + waitAfterCrash)) &&
                        (contacts.Count != 0))
                    {
                        reset();
                    }

                    foreach (Contact contact in contacts)
                    {
                        if (oldContacts.ContainsKey(contact.Id))
                        {
                            //Airplane tmpPlane;
                            //oldContacts.TryGetValue(contact.Id, out tmpPlane);
                            //tmpPlane.AppendRoute(new Vector2(contact.CenterX, contact.CenterY));
                            oldContacts[contact.Id].AppendRoute(new Vector2(contact.CenterX, contact.CenterY));
                            if (OnTarget(new Vector2(contact.CenterX, contact.CenterY), airportLocation))
                            {
                                oldContacts.Remove(contact.Id);
                                bannedId = contact.Id;
                            }
                        }
                        else if (contact.IsFingerRecognized)
                        {
                            foreach (Airplane plane in airplanes)
                            {
                                if (OnTarget(plane.Location, new Vector2(contact.CenterX, contact.CenterY)) &&
                                    !oldContacts.ContainsValue(plane) && !oldContacts.Keys.Contains(contact.Id) && contact.Id != bannedId)
                                {
                                    plane.Route.Clear();
                                    plane.Goal = Vector2.Zero;
                                    oldContacts.Add(contact.Id, plane);
                                }//end if
                            }//end foreach airplanes
                        }// end else if

                        //airplanes.ElementAt(0).AppendRoute(new Vector2(contact.CenterX, contact.CenterY));
                    }// end foreach contacts
                    Dictionary<int, Airplane> iteratorOldContacts = oldContacts;
                    oldContacts = new Dictionary<int, Airplane>();
                    if (iteratorOldContacts.Count != 0)
                    {
                        foreach (int contact in iteratorOldContacts.Keys)
                        {
                            if (contacts.Contains(contact))
                            {
                                oldContacts.Add(contact, iteratorOldContacts[contact]);
                            }
                        }
                    }

                }

                PlaneUpdateLogic();
                // TODO: Add your update logic here
            }
            else
            {
                Exit();
            }

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the app should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            if (!applicationLoadCompleteSignalled)
            {
                // Dismiss the loading screen now that we are starting to draw
                ApplicationLauncher.SignalApplicationLoadComplete();
                applicationLoadCompleteSignalled = true;
            }

            if (crashed)
            {
                CrashScreen();
                base.Draw(gameTime);
                return;
            }
            

            //TODO: Rotate the UI based on the value of screenTransform here if desired
            Airplane planeToBeDeleted = null;
            graphics.GraphicsDevice.Clear(backgroundColor);

            airSprite.Begin();

            
            airSprite.Draw(new Texture2D(GraphicsDevice, 1, 1, 1, TextureUsage.None, SurfaceFormat.Color),
                                new Vector2(300,300),
                                null,
                                Color.White,
                                0,
                                Vector2.Zero,
                                new Vector2(300, 1),
                                SpriteEffects.None,
                                2f);
            //draw airport
            airSprite.Draw(airport,airportLocation, null, Color.White, 0f, new Vector2(airport.Width/2, airport.Height/2), new Vector2(2f, 2f), SpriteEffects.None, 1f);

            //draw airplanes and routes
            foreach (Airplane plane in airplanes)
            {
                plane.SetNextLocation();
                if (OnTarget(plane.Location, airportLocation))
                {
                    planeToBeDeleted = plane;
                }
                foreach (Airplane planeColTest in airplanes)
                {
                    if (planeColTest != plane)
                    {
                        if (PlaneOnTarget(plane.Location, planeColTest.Location))
                        {
                            crashed = true;
                            timeOfDeath = (int)(gameTime.TotalGameTime.TotalSeconds);
                            plane.Color = Color.Red;
                            planeColTest.Color = Color.Red;
                            CheckHighscore();
                        }
                    }
                }

                //drawPathsQueue = (Queue<Vector2>)drawPathsQueue.Concat(plane.Route);
                //drawPathsQueue.Union(plane.Route);
                foreach (Vector2 pos in plane.Route)
                {
                    airSprite.Draw(airplaneRoute, pos, null, Color.White, 0f, new Vector2(airplaneRoute.Width / 2, airplaneRoute.Height / 2), new Vector2(1F, 1F), SpriteEffects.None, 0f);
                }
                airSprite.Draw(airplane, plane.Location, null, plane.Color, plane.Rotation, new Vector2(airplane.Width / 2, airplane.Height / 2), new Vector2(1F, 1F), SpriteEffects.None, 0f);
                
            }

            if (planeToBeDeleted != null)
            {
                score++;
                if (highscore < score)
                    highscore = score;
                airplanes.Remove(planeToBeDeleted);
                foreach (int key in oldContacts.Keys)
                {
                    if (oldContacts[key] == planeToBeDeleted)
                    {
                        oldContacts.Remove(key);
                        break;
                    }
                }
            }
            String scoreString = "Score: " + score + "\nHighscore: " + highscore;
            //Draws score
            Vector2 scoreCenter = courierNew.MeasureString(scoreString) / 2;
            //top left
            airSprite.DrawString(courierNew, scoreString, new Vector2(200, 50), Color.Blue, (float)Math.PI, scoreCenter, 1f, SpriteEffects.None, 0f);
            //bottom right
            airSprite.DrawString(courierNew, scoreString, new Vector2(surfaceWidth - 200, surfaceHeight - 50), Color.Blue, 0f, scoreCenter, 1f, SpriteEffects.None, 0f);
  


            airSprite.End();

            //TODO: Add your drawing code here
            //TODO: Avoid any expensive logic if application is neither active nor previewed

            base.Draw(gameTime);
        }

        #endregion

        #region Application Event Handlers

        /// <summary>
        /// This is called when application has been activated.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnApplicationActivated(object sender, EventArgs e)
        {
            // Update application state.
            isApplicationActivated = true;
            isApplicationPreviewed = false;

            // Orientaton can change between activations.
            ResetOrientation();

            //TODO: Enable audio, animations here

            //TODO: Optionally enable raw image here
        }

        /// <summary>
        /// This is called when application is in preview mode.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnApplicationPreviewed(object sender, EventArgs e)
        {
            // Update application state.
            isApplicationActivated = false;
            isApplicationPreviewed = true;

            //TODO: Disable audio here if it is enabled

            //TODO: Optionally enable animations here
        }

        /// <summary>
        ///  This is called when application has been deactivated.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnApplicationDeactivated(object sender, EventArgs e)
        {
            // Update application state.
            isApplicationActivated = false;
            isApplicationPreviewed = false;

            //TODO: Disable audio, animations here

            //TODO: Disable raw image if it's enabled
        }

        #endregion

        #region IDisposable

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                // Release managed resources.
                IDisposable graphicsDispose = graphics as IDisposable;
                if (graphicsDispose != null)
                {
                    graphicsDispose.Dispose();
                }
                if (contactTarget != null)
                {
                    contactTarget.Dispose();
                    contactTarget = null;
                }
            }

            // Release unmanaged Resources.

            // Set large objects to null to facilitate garbage collection.

            base.Dispose(disposing);
        }


        #endregion

    }
}
